/*
 * Copyright 2009 Yannick Stucki (yannickstucki.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.yannickstucki.android.musicqueue;

import java.util.ArrayList;
import java.util.LinkedList;

import android.app.Activity;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

/**
 * The main class. Handles the UI for both the portrait and landscape view.
 * 
 * TODO: maybe separate logic.
 * 
 * @author Yannick Stucki (yannickstucki@gmail.com)
 * 
 */
public class MusicQueue extends ListActivity implements DialogHandler,
    OptionsMenuHandler, ContextMenuHandler {

  /**
   * Name of the shared preferences.
   */
  public static final String PREFERENCES_NAME = "preferences";
  
  /**
   * TODO: Only temporary.
   */
  public boolean destroyed = false;

  /**
   * An adapter for the list in the home view.
   * 
   */
  private SongAdapter adapter;

  /**
   * A reference to the toast object so old toasts can be canceled.
   * 
   * TODO: canceling toast doesn't seem to work nicely. Investigate
   */
  private Toast toast;

  /**
   * A reference to the listView of this ListActivity. This way getListView()
   * doesn't have to be called multiple times.
   */
  private ListView listView;

  /**
   * A reference to the song manager.
   */
  private SongManager songManager;

  /**
   * A reference to the service handling the music play.
   */
  private PlayerService playerService;

  /**
   * True if this view is in the landscape mode. Even though the landscape mode
   * is totally different, the activity is the same since loading a new activity
   * would take too long.
   */
  private boolean landscape = false;

  /**
   * Seems to prevent progress updater starting once in the beginning. TODO:
   * maybe we can get rid of it.
   */
  private boolean updateUI = false;

  /**
   * Intent used to start {@link PlayerService} to play music.
   */
  private Intent serviceIntent;

  /**
   * Progress the last song made (if any) when the program exited.
   */
  private int savedProgress = 0;

  /**
   * A reference to the menu item to clear the queue is needed because it can be
   * disabled if there's no items in the queue.
   */
  private MenuItem clearQueue;

  /**
   * Manages the options menu.
   */
  private OptionsMenuManager optionsMenuManager = new OptionsMenuManager(this);

  /**
   * Manages the context menu.
   */
  private ContextMenuManager contextMenuManager = new ContextMenuManager(this);

  // Settings		
  /**		
   * If true, the head set button is used to control music queue.		
   */
  private boolean enableHeadsetButton;

  /**
   * Handles the UI in portrait mode.
   */
  public PlayerUiPortrait playerUiPortrait;

  /**
   * Handles the UI in landscape mode.
   */
  private PlayerUiLandscape playerUiLandscape;

  /**
   * A filter to notify when the SD card scan starts.
   */
  private IntentFilter sdScanStart;
  
  /**
   * A filter to notify when the SD card scan stops.
   */
  private IntentFilter sdScanStop;
  
  /**
   * A filter to notify when the SD card is busy.
   */
  private IntentFilter sdBusy;
  
  /**
   * Belongs to {@link #sdScanStart}.
   */
  private BroadcastReceiver sdScanStartReceiver;
  
  /**
   * Belongs to {@link #sdScanStop}.
   */
  private BroadcastReceiver sdScanStopReceiver;
  
  /**
   * Belongs to {@link #sdBusy}.
   */
  private BroadcastReceiver sdBusyReceiver;

  /**
   * TODO: can be removed and made local again... I think..
   */
  private SharedPreferences settings;
  
  /**
   * Connection between Activity and Service.
   */
  private ServiceConnection connection = new ServiceConnection() {

    private Handler handler = new Handler();

    private Runnable updateResults = new Runnable() {
      public void run() {
        onSongsUpdated();
      }
    };
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
      Logger.out("Service connected");
      // some service connection stuff
      playerService = ((PlayerService.LocalBinder) service).getService();
      playerService.setMusicQueue(MusicQueue.this);

      // TODO: change this... can be done more elegantly...

      if (playerService.getSongManager() == null && songManager == null) {
        songManager = new SongManager(playerService, new DbAdapter(
            MusicQueue.this));
        playerService.setSongManager(songManager);
        songManager.init(MusicQueue.this, handler, updateResults);

        // SharedPreferences settings = getSharedPreferences("savedState", 0);
        // savedProgress = settings.getInt("progress", 0);
               
      } else {
        if (songManager != null) {
          playerService.setSongManager(songManager);
        } else {
          songManager = playerService.getSongManager();
        }
        onSongsUpdated();
      }
    }

    private void onSongsUpdated() {
      if (!landscape) {
        adapter = new SongAdapter(MusicQueue.this, songManager,
            songManager.songs);
        songManager.setAdapter(adapter);
      }
      // Handle the case of no songs
      if (songManager == null || songManager.getAllSongs().length == 0) {
        Toast.makeText(MusicQueue.this,
            MusicQueue.this.getResources().getString(R.string.no_songs_text),
            Toast.LENGTH_LONG).show();

        if (!landscape) {
          if (playerUiPortrait != null) {
            playerUiPortrait.setUiToInvisible();
          }
        }
        return;
      } else {
        if (!landscape) {
          if (playerUiPortrait != null) {
            playerUiPortrait.setUiToVisible();
          }
        }
      }
      // ////////////////////////////////////////////////////////////////////////

      // The adapter setting: TODO: this will need major changes with new
      // features
      if (landscape) {
        Song.currentDisplayStringIndex = Song.DISPLAY_INDEX_LANDSCAPE;
        playerUiLandscape = new PlayerUiLandscape(MusicQueue.this, listView,
            new SongAdapter(MusicQueue.this, songManager, songManager
                .getAllSongs()), new SongAdapter(MusicQueue.this, songManager,
                songManager.getStarredSongs()),
            (ImageButton) findViewById(R.id.allsongs),
            (ImageButton) findViewById(R.id.starredsongs), MusicQueue.this
                .getResources());

        playerUiLandscape.initUi();
      } else {
        Song.currentDisplayStringIndex = Song.DISPLAY_INDEX_PORTRAIT;
        listView.setAdapter(adapter);
        playerUiPortrait = new PlayerUiPortrait((TextView) MusicQueue.this
            .findViewById(R.id.time), (TextView) MusicQueue.this
            .findViewById(R.id.duration), (SeekBar) MusicQueue.this
            .findViewById(R.id.seekbar), (ImageButton) MusicQueue.this
            .findViewById(R.id.playpause), (TextView) MusicQueue.this
            .findViewById(R.id.current), (Button) MusicQueue.this
            .findViewById(R.id.queuesize), (ImageButton) MusicQueue.this
            .findViewById(R.id.shuffle), listView, songManager, toast);
        playerUiPortrait.initUi(MusicQueue.this, playerService);
        playerService.listUpdated();
      }

      playerService.guiResume();
    }

    public void onServiceDisconnected(ComponentName className) {
      playerService = null;
    }
  };

  @Override
  public Object onRetainNonConfigurationInstance() {
    return songManager;
  }

  /**		
   * Returns whether the head set button is enabled or not.		
   */		
  public boolean getEnableHeadsetButton() {		
    return enableHeadsetButton;		
  }		

  /**
   * Returns the progress of the last played song and sets it to 0 since it's
   * only used once.
   */
  public int getSavedProgress() {
    int savedProgressTemp = savedProgress;
    savedProgress = 0;
    return savedProgressTemp;
  }

  /**
   * True if the UI is visible and should be updated periodically when the song
   * is playing.
   */
  public boolean getUpdateUi() {
    return updateUI;
  }

  /**
   * Returns true if the view is in landscape.
   */
  public boolean getLandscape() {
    return landscape;
  }

  /**
   * A new starred song manager might be needed since the starred items can
   * change.
   */
  public SongAdapter createNewStarredSongAdapter() {
    return new SongAdapter(this, songManager, songManager.getStarredSongs());
  }
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    songManager = (SongManager) MusicQueue.this
        .getLastNonConfigurationInstance();
    Logger.out("MusicQueue.onCreate");
    requestWindowFeature(Window.FEATURE_NO_TITLE);

    // Clear the
    settings = this.getSharedPreferences(PREFERENCES_NAME, Activity.MODE_PRIVATE);
    enableHeadsetButton = settings.getBoolean("enable_headset_button", true);

    // landscape is central to this app: This means the user is adding songs
    if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
      landscape = true;
      setContentView(R.layout.landscape);
    } else {
      setContentView(R.layout.main);
    }
    // binding the Service
    serviceIntent = new Intent(MusicQueue.this, PlayerService.class);
    startService(serviceIntent);
    bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
    listView = this.getListView();
    registerForContextMenu(listView);
    sdScanStart = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
    sdScanStop = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    sdBusy = new IntentFilter(Intent.ACTION_MEDIA_UNMOUNTED);
    sdBusy.addAction(Intent.ACTION_MEDIA_EJECT);
    sdBusy.addDataScheme("file");
    sdScanStartReceiver = new BroadcastReceiver() {

      @Override
      public void onReceive(Context context, Intent intent) {
        Logger.debug("started");
        Toast.makeText(MusicQueue.this, "Started scanning SD card.",
            Toast.LENGTH_LONG);
      }
    };

    sdScanStopReceiver = new BroadcastReceiver() {

      @Override
      public void onReceive(Context context, Intent intent) {
        if (songManager != null) {
          songManager.updateSongList(MusicQueue.this, null);
        }
        if (landscape) {
          playerUiLandscape.notifyDataSetChanged();
        } else {
          adapter.notifyDataSetChanged();
        }
      }
    };

    sdBusyReceiver = new BroadcastReceiver() {

      @Override
      public void onReceive(Context context, Intent intent) {
        Logger.debug("open busy");
        Intent activity = new Intent();
        activity.setClass(MusicQueue.this, SdCardBusyScreen.class);
        startActivity(activity);
      }
    };

    sdScanStart.addDataScheme("file");
    sdScanStop.addDataScheme("file");

    if (!Environment.getExternalStorageState()
        .equals(Environment.MEDIA_MOUNTED)) {
      Intent activity = new Intent();
      activity.setClass(MusicQueue.this, SdCardBusyScreen.class);
      startActivity(activity);
    }
  }

  @Override
  protected void onStart() {
    super.onStart();
    Logger.out("MusicQueue.onStart");
    updateUI = true;
  }

  @Override
  protected void onResume() {
    super.onResume();
    Logger.out("MusicQueue.onResume");
    if (!landscape) {
      updateUI = true;
      // Not sure if this is needed, but the GUI sometimes failed to update, so
      // I put it in again.
      if (playerService != null) {
        playerService.guiResume();
      }
    }
    registerReceiver(sdBusyReceiver, sdBusy);
  }

  @Override
  protected void onPause() {
    super.onPause();
    Logger.out("MusicQueue.onPause");
    updateUI = false;
    if (playerService != null) {
      playerService.stopProgressUpdater();
    }
    unregisterReceiver(sdBusyReceiver);
  }

  @Override
  protected void onStop() {
    super.onStop();
    Logger.out("MusicQueue.onStop");
    final boolean isPlayerService = playerService != null;

    // Save preferences
    if (songManager != null) {
      if (!songManager.isEmpty()) {
        final int qSize = songManager.waitingQueue.size();
        SharedPreferences.Editor editor = settings.edit();
        editor.clear();
        int songHandlerCount = SongManager.COUNT;
        Song[] songs = songManager.songs;
        for (int i = 0; i < songHandlerCount; i++) {
          editor.putInt("songs" + i, songs[i].index);
        }
        editor.putInt("nWaiting", qSize);
        LinkedList<Song> waitingQueue = songManager.waitingQueue;
        for (int i = 0; i < qSize; i++) {
          editor.putInt("waiting" + i, waitingQueue.get(i).index);
        }
        if (isPlayerService) {
          editor.putInt("progress", playerService.getCurrentPosition());
        }
        editor.commit();
      }
    }
    if (isPlayerService) {
      if (!playerService.isPlaying()) {
        this.stopService(serviceIntent);
      }
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    this.destroyed = true;
    if (playerService == null || playerService.destroyed) {
      songManager.close();
    }
    unbindService(connection);

    // unregisterReceiver(sdScanStopReceiver);
    // Logger.out("MusicQueue.onDestroy");
  }

  @Override
  public void onListItemClick(ListView parent, View v, int position, long id) {
    if (landscape) {
      songClickedLandscape(parent, position);
    } else { // only possible with trackball from here. This function can also
      // be triggered by the on touch event in PlayerUiPortrait.
      playerUiPortrait.songClicked(position);
    }
  }

  /**
   * When an song gets clicked in landscape it gets added to the waiting queue.
   */
  private void songClickedLandscape(ListView parent, int position) {
    Song song = (Song) parent.getItemAtPosition(position);
    songManager.waitingQueue.addLast(song);
    if (toast != null) {
      toast.cancel();
    }
    toast = Toast.makeText(this, getString(R.string.enqueued) + " "
        + song.title, Toast.LENGTH_SHORT);
    toast.show();
  }

  // Options
  // Menu/////////////////////////////////////////////

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    if (landscape) {
      optionsMenuManager.createOptionsMenuLandscape(menu, getResources());
    } else {
      clearQueue = optionsMenuManager.createOptionsMenu(menu, getResources());
      if (songManager != null && songManager.waitingQueue.size() == 0) {
        clearQueue.setEnabled(false);
      }
    }
    return super.onCreateOptionsMenu(menu);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    return optionsMenuManager.optionsItemSelected(item);
  }

  @Override
  public void clearQueue() {
    songManager.waitingQueue.clear();
    clearQueue.setEnabled(false);
    playerUiPortrait.setQueueSize(0);
  }

  @Override
  public void enqueueFullList() {
    int itemCount = listView.getAdapter().getCount();
    ArrayList<Song> arrayList = new ArrayList<Song>();
    for (int i = 0; i < itemCount; i++) {
      arrayList.add((Song) listView.getItemAtPosition(i));
    }
    int[] permutation = RandomSequence.get(itemCount); // let's
    // randomize
    LinkedList<Song> waitingQueue = songManager.waitingQueue;
    for (int i = 0; i < itemCount; i++) {
      waitingQueue.addLast(arrayList.get(permutation[i]));
    }
  }

  /**
   * Sets whether the menu entry to clear the queue is enabled or not.
   * 
   * @param clearQueueEnabled
   */
  public void setClearQueueEnabled(boolean clearQueueEnabled) {
    if (clearQueue != null) {
      clearQueue.setEnabled(clearQueueEnabled);
    }
  }

  @Override
  public void onCreateContextMenu(ContextMenu menu, View v,
      ContextMenuInfo menuInfo) {
    contextMenuManager.createContextMenu(menu, landscape, getResources());
    super.onCreateContextMenu(menu, v, menuInfo);
  }

  @Override
  public boolean onContextItemSelected(MenuItem item) {
    return contextMenuManager.contextItemSelected(item);
  }

  // Handling the context menu

  @Override
  public void playSong(int position) {
    Song song;
    if (!landscape) {
      if (position == 0) { // we're already playing this
        if (!playerService.isPlaying()) {
          playerService.playPause();
        }
        return;
      }
      song = songManager.songs[position];
      songManager.remove(position);
    } else {
      song = (Song) listView.getItemAtPosition(position);
    }
    songManager.songs[0] = song;
    // songManager.items[0] = song.title;
    if (!landscape) {
      adapter.notifyDataSetChanged();
    }
    playerService.listUpdated();
  }

  @Override
  public void removeSong(int position) {
    songManager.remove(position);
  }

  @Override
  public void openWebsite(Uri uri) {
    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    startActivity(intent);
  }

  @Override
  public void refreshLibrary() {
    registerReceiver(sdScanStartReceiver, sdScanStart);
    registerReceiver(sdScanStopReceiver, sdScanStop);
    sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
        + Environment.getExternalStorageDirectory())));
  }

  @Override
  public void showSettings() {
    Intent i = new Intent();
    i.setClass(MusicQueue.this, SettingsScreen.class);
    startActivity(i);
  }

  @Override		
  protected Dialog onCreateDialog(int id) {		
    return new DialogManager().createDialog(this, this, id, enableHeadsetButton);		
  }
}

